/*
 *	socketcomm.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include "sockcomADT.h"
#include "sockets.h"
#include "../clsv/clsv.h"
#include "../error/error.h"

#define QLEN		64
#define PORT_NUM 	33

#define	MAX(x, y)	((x) > (y) ? (x) : (y))

/* Concret data type */
struct CommCDT {
  int msock[PORT_NUM]; 
  int ssock;
  int usock[PORT_NUM];
  int cusock;
  int nfds;
  fd_set rfds;
  commType type; 
  struct sockaddr_in addr;
  int socknum;
  int socksel;
};

static int read_data(Comm comm, void *p, int qty, int service);

Comm
newComm(void)
{
	Comm comm;

	if( (comm= calloc(1, sizeof(struct CommCDT))) != NULL );
	{
		comm->ssock = -1;
		/* clears the set */
		FD_ZERO(&(comm->rfds));
	}
	return comm;
}

int 
initComm(Comm comm, commType type, char * host, char * port )
{
	int end= 1;

    if (comm != NULL)
	{
        if (type == COMM_CLIENT)
        {	/* if client, only need to connect TCP */
        	if( (comm->ssock = connectTCP(host, port)) < 0 )
			{
            	comm->type = type;
            	return -1;
			}
        }
		else
		{	/* if server, need to set UDP and TCP sockets */
            if ((comm->msock[comm->socknum] = passiveTCP(port, QLEN, host)) < 0)
            	end= -1;

           	if( (comm->usock[comm->socknum] = passiveUDP(port, host)) < 0 )
           		end= -1;
           	comm->nfds = MAX(comm->nfds, MAX(comm->msock[comm->socknum], 
           									comm->usock[comm->socknum])) + 1;
			comm->socknum++;
		}
		/* set comm type */
        comm->type = type;
    }
    return end;
}

int
setUDP(Comm comm, char * host, char * port)
{
	/* set UDP socket */
	return comm->cusock = connectUDP(host, port);
}

void
unsetUDP(Comm comm)
{
	close(comm->cusock);
	return;
}

int
sockSel(Comm comm)
{
	int i;
	
	for(i = 0; i < comm->socknum; i++)
	{
		/* clears the sets */
		FD_SET(comm->msock[i], &(comm->rfds));
		FD_SET(comm->usock[i], &(comm->rfds));
	}
	
	if( select(comm->nfds, &(comm->rfds), (fd_set *)0, (fd_set *)0,
			(struct timeval *)0) < 0 )
			return 0;
	/* check if any socket is available to read */
	for(i = 0; i < comm->socknum; i++)
	{
		if( FD_ISSET(comm->msock[i], &(comm->rfds)) )
		{
			comm->socksel = i;
			return 1;
		}
	
		if( FD_ISSET(comm->usock[i], &(comm->rfds)) )
		{
			comm->socksel = i;
			return 2;
		}
	}
	return -1;
}

void 
send_packet(Comm comm, const void * p, int qty, int service)
{
    int sock;
	short aux;
	void * packet;
	
	if (service == _TCP_)
		sock = comm->ssock;
	else
	{
		if(!comm->socksel)
			sock = comm->cusock;
		else
			sock = comm->usock[comm->socksel];
	}
	/* make space for qty to send */
	aux = htons(qty);
	if ((packet = malloc(qty+sizeof(aux))) == NULL)
	{
		fprintf(stderr, "Memory allocation error\n");
		return;
	}
	
	/* build the packet */
	memcpy(packet, &aux, sizeof(aux));
	memcpy((char *)packet+sizeof(aux), p, qty);
	
	/* send the size (in bytes) of the packet and the packet itself */
   	write(sock, packet, qty+sizeof(aux));

	free(packet);
    return;
}

int 
receive_packet(Comm comm, void * p, int lim, int service )
{
    int qty, ret = 1;
	short aux;
   	void * p2;
	
	if (service == _TCP_)
    {
		/* determine how many bytes are to be read from the packet */
    	if ((ret = read_data(comm, &aux, sizeof(aux), service)) < 0)
    		return -ERROR_CONNECTION_FAILURE;
		
		/* the connection has been closed */
    	if (!ret)
    		return -ERROR_CONNECTION_FAILURE;
		
		if (!ret && errno != 0)
		{
			return -ERROR_CONNECTION_FAILURE;
		}
		/* tranlate the packet to read size */
		qty = ntohs(aux);
	    if (qty > lim)
    		return -ERROR_CONNECTION_FAILURE;
		
		/* read the packet from the socket */
	    if ((ret = read_data(comm, p, qty, service)) < 0)
    		return -ERROR_CONNECTION_FAILURE;
		
		if (qty <= lim && ret)
			return qty;
		else
    		return -ERROR_CONNECTION_FAILURE;
    }
    else
    {
    	/* I don't need de first two bytes if it's an UDP datagram */
    	if ((p2 = malloc(lim+sizeof(aux))) == NULL)
    		return !OK;
  		if (read_data(comm, p2, lim+sizeof(aux), service) < 0)
  		{
  			free(p2);
  			return -ERROR_CONNECTION_FAILURE;
  		}
  		/* copy the received stream to the answer parameter */
    	memcpy(p, ((char *)p2)+sizeof(aux), lim);
    	free(p2);
    }
    return OK;
}

int 
receive_UDP(Comm comm, void *p, int lim )
{
    int ret, alen= sizeof(struct sockaddr_in);
    void * p2;

	/* make space for the packet to receive */
    if( (p2= malloc(lim+sizeof(short))) == NULL )
    	return -1;

	if ((ret= recvfrom(comm->usock[comm->socksel], p2, lim+sizeof(short), 0, 
			(struct sockaddr *)&(comm->addr), (socklen_t *)&alen)) < 0)
	{
		fprintf(stderr, "recvfrom: error reading UDP datagram.\n");
		free(p2);
		return -1;
	}
	/* I don't need de first two bytes if it's an UDP datagram */
    memcpy(p, ((char *)p2)+sizeof(short), lim);
	free(p2);
	return 1;
}

void
send_UDP(Comm comm, void * stream, int lim)
{
	short aux;
	void * packet;

	/* make space for qty to send */
	aux = htons(lim);
	if ((packet = malloc(lim+sizeof(aux))) == NULL)
	{
		fprintf(stderr, "Memory allocation error\n");
		return;
	}
	
	/* build the packet */
	memcpy(packet, &aux, sizeof(aux));
	memcpy((char *)packet+sizeof(aux), stream, lim);

	sendto(comm->usock[comm->socksel], packet, lim+sizeof(aux), 0, 
				(struct sockaddr *)&(comm->addr), sizeof(struct sockaddr_in));

	free(packet);
	return;
}

static int
read_data(Comm comm, void *p, int qty, int service)
{
    int current= 0, n, tot = 0;
	
	if (service == _TCP_)
	{	/* read until there is nothing left to read */
	    for (current = 0; current < qty; current += n)
		{
			n = recv(comm->ssock, p, qty - current, MSG_WAITALL);
			tot += n;
	   		if (n < 0)
				return n;

   	   		if (n == 0 && current != qty)
				return 0;
		}
    }
	else
	{	/* read the UDP datagram */
		if( (n = recv(comm->cusock, p, qty - current, MSG_WAITALL)) < 0 )
			return n;
	}
	return 1;
}

void 
closeComm(Comm comm, sockType sock) 
{
	int i;
	if (sock == SERVER )
	{
		/* close all the master sockets opened to listen on the different 
		 *  ports*/
		for(i = 0; i < comm->socknum; i++)
		{
			if(comm->msock[i])
				close(comm->msock[i]);
			comm->msock[i] = 0;
			
			if(comm->msock[i])
				close(comm->usock[i]);
			comm->usock[i] = 0;
		}
    }
    if (sock == CLIENT && comm->ssock != -1 )
    {
        close(comm->ssock);
        comm->ssock = -1;
    }
}

int acceptComm(Comm comm) 
{
    struct  sockaddr_in fsin;   /* the address of a client  */
    unsigned int alen = sizeof(fsin);
    unsigned int * pfsin = NULL;

	/* i only accept connections requests in the server */
    if (comm->type == COMM_SERVER)
        comm->ssock = accept(comm->msock[comm->socksel], 
        	(struct sockaddr *)pfsin, &alen);

    return comm->ssock;
}

char *
printIP(Comm comm)
{
	socklen_t fromlen;

	fromlen= sizeof(struct sockaddr_in);
	/* get IP */
	getpeername(comm->ssock, (struct sockaddr *)&(comm->addr), &fromlen);
	/* convert the IP to a string and return it */
	return inet_ntoa(comm->addr.sin_addr);
}
